#ifndef _RAY_TRACER_H_
#define _RAY_TRACER_H_
#include "common.h"
#include <vector>
#include <string>
#include "boost/ptr_container/ptr_vector.hpp"
#include <cstring>
#include "tinyxml.h"
namespace raytracer
{	
	class Light
	{
	public:
		//light type
		enum LightType{Point,Line};
		Light(const vector3& color,LightType type):mColor(color),mType(type){}
		//get the ray from the light source to the point
		vector3 GetDirAt(const vector3& point)const
		{
			vector3 dir;
			if(Point==mType)
			{				
				dir=point-mCenter;				
			}			
			return dir;
		}

		void SetColor(const vector3& color){mColor=color;}
		const vector3& GetColor()const{return mColor;}

		void SetCenter(const vector3& center){this->mCenter=center;}
		const vector3& GetCenter()const{return mCenter;}

		void SetDirection(const vector3& dir){mDir=dir;}
		const vector3& GetDirection()const{return mDir;}

		void SetAngle(float angle){mAngle=angle;}
		float GetAngle()const{return mAngle;}

		void SetLightType(LightType type){mType=type;}
		LightType GetLightType()const{return mType;}

	private:
		vector3 mColor;
		vector3 mCenter;
		vector3 mDir;
		float mAngle;
		LightType mType;
	};

	//class PointLight:public Light
	//{
	//private:
	//	vector3 mCenter;
	//public:
	//	PointLight(const vector3& color,const vector3& center):Light(color),mCenter(center){}

	//void setCenter(const vector3& center){this->mCenter=center;}
	//vector3& getCenter(){return mCenter;}

	////get the ray from the light source to the point
	//virtual Ray GetRayAt(const vector3& point)
	//{
	//	Ray ray;
	//	ray.SetOrigin(mCenter);
	//	ray.SetDirection(point-mCenter);
	//	ray.GetDirection().Normalize();
	//	return ray;
	//}
	//};


	class Primitive
	{
	protected:
		Material mMaterial;
	public:
		Primitive(const Material& material):mMaterial(material){}
		Primitive(){}
		//get the normal at the given point
		//virtual vector3 GetNormalAt(const vector3& point)const=0;
		//get the distance from the start point along the dir of the ray, less than or equal to zero means no intersection
		//virtual double Intersect(const Ray& ray)const=0;
		//test whether they intersect
		//virtual bool TestIntersect(const Ray& ray)const=0;


		//a method for intersection detection
		//if any of the pointer is NULL, the corresponding result is not returned
		//pisIntted-indicateds if they are intersected, this must not be NULL
		//pLen-length from the origin to the intersection point
		//pInttPt-intersection point
		//pNormal-Normal at intersection point
		virtual void Intersect(const Ray& ray,bool* pIsIntted,double* pLen=NULL,vector3* pInttPt=NULL,vector3* pNormal=NULL)const=0;

		//getter and setter of material
		const Material& GetMaterial()const{return mMaterial;}
		void SetMaterial(const Material& material){mMaterial=material;}
	};

	class Sphere:public Primitive
	{
	private:
		vector3 mCenter;
		float mRadius;
	public:
		Sphere(const Material& material,const vector3& center,float radius):Primitive(material),mCenter(center),mRadius(radius){}
		Sphere(const TiXmlElement* ele);
		//vector3 GetNormalAt(const vector3& point)const{vector3 normal = point-mCenter;normal.Normalize();return normal;}
		////implemented
		//double Intersect(const Ray& ray)const;
		////implemented
		//bool TestIntersect(const Ray& ray)const{return Intersect(ray)>0;}
		void Intersect(const Ray& ray,bool* pIsIntted,double* pLen=NULL,vector3* pInttPt=NULL,vector3* pNormal=NULL)const;
		void SetCenter(const vector3& center){mCenter=center;}
		void SetRadius(float value){mRadius=value;}
		const vector3& GetCenter()const{return mCenter;}
		float GetRadius()const{return mRadius;}
	};

	class Plane:public Primitive
	{
	private:
		vector3 mNormal;
		//distance from the origin
		float mDist;
	public:
		Plane(const Material& material,const vector3& normal,float dist):Primitive(material),mNormal(normal),mDist(dist){mNormal.Normalize();}
		Plane(const TiXmlElement* ele);
		//vector3 GetNormalAt(const vector3& point)const{return mNormal;}
		void Intersect(const Ray& ray,bool* pIsIntted,double* pLen=NULL,vector3* pInttPt=NULL,vector3* pNormal=NULL)const;
		/*double Intersect(const Ray& ray)const;
		bool TestIntersect(const Ray& ray)const{return Intersect(ray)>0;}*/
	};

	class Scene
	{
	private:
		boost::ptr_vector<Light> mLights;
		boost::ptr_vector<Primitive> mPrimitives;
		//width and height of the target image
		int mWidth,mHeight;
		// position of the eye
		vector3 mEyePos;
		/*
		 *for the sake of simplicity, we set the view plane as the plane of xy plane,
		 *mWX,mWY is the right-upper corner of the view plane
		 *mWWidth,mWHeight is the width and height of the view plane
		*/
		float mWX,mWY,mWWidth,mWHeight;
		//since the ray tracer is recursive, the mMaxStep limits the depth of the recursion
		int mMaxStep;
		//buffer for target image
		int* mColorBuffer;
		//ambient
		vector3 mAmbient;
		//attenuation index,value at position 0 is quadratic, the others in turn
		vector3 mAttIdx;
	public:
		Scene():mColorBuffer(NULL){}
		bool InitScene(const std::string& filename);
		void Render();

		//for creating image
		const int* GetColorBuffer()const{return mColorBuffer;}
		int GetWidth()const{return mWidth;}
		int GetHeight()const{return mHeight;}
	private:
		Primitive* RayTrace(Ray& ray,vector3& color, int depth, float refrIndex, float& dist);
	};


	class Triangle:public Primitive
	{
	private:
		vector3 mCoords[3];
		vector3 mNormal;
	public:
		Triangle(const Material& m,const vector3 points[3]);
		Triangle(const TiXmlElement* ele);
		void Intersect(const Ray& ray,bool* pIsIntted,double* pLen=NULL,vector3* pInttPt=NULL,vector3* pNormal=NULL)const;
	private:
		void InitNormal(){mNormal = (mCoords[1]-mCoords[0]).Cross(mCoords[2]-mCoords[0]);mNormal.Normalize();}
	};

	//class Polyhedron:public Primitive
	//{
	//public:
	//	typedef std::vector<vector3> PointV;
	//	typedef std::vector<PointV::size_type> IndexV;
	//private:
	//	PointV mPoints;
	//	//this stores the indexes of points of triangles,so the size of mIndexes can be divided by 3
	//	//the points of every triangle must be in anti-clockwise
	//	IndexV mIndexes;
	//	//normals for the planes of the triangles
	//	PointV mNormals;
	//public:
	//	Polyhedron(const Material&m,const PointV& points,const IndexV& indexes);
	//	void Intersect(const Ray& ray,bool* pIsIntted,double* pLen=NULL,vector3* pInttPt=NULL,vector3* pNormal=NULL)const;
	//};
	class Cylindar:public Primitive
	{
	public:
		Cylindar(const TiXmlElement* ele);
		void Intersect(const Ray& ray,bool* pIsIntted,double* pLen=NULL,vector3* pInttPt=NULL,vector3* pNormal=NULL)const;
	private:
		//bottom center
		vector3 mBCenter;
		//vertical direction,normalized
		vector3 mVDir;
		float mHeight;
		//radius for bottom and top circle
		float mRadius;
	};
}
#endif